home *** CD-ROM | disk | FTP | other *** search
/ Amiga Developer CD 2.1 / Amiga Developer CD v2.1.iso / Reference / Amiga_Mail_Vol2 / Archives / Plain / ma92.lha / AmNet / Amnet.txt < prev    next >
Encoding:
Text File  |  1992-03-08  |  27.2 KB  |  703 lines

  1. (c)  Copyright 1992 Commodore-Amiga, Inc.   All rights reserved.
  2. The information contained herein is subject to change without notice,
  3. and is provided "as is" without warranty of any kind, either expressed
  4. or implied.  The entire risk as to the use of this information is
  5. assumed by the user.
  6.  
  7.  
  8. A Shared Socket Library Server and Client
  9.  
  10.  
  11. by John Wiederhirn and John Orr
  12.  
  13.  
  14.  
  15.  
  16. Wednesday is gyro day in CATS, so we have to go out to lunch and eat
  17. gyros.  Because David the Engineer used to work in CATS, we are morally
  18. obligated to bring him along.  Like the rest of the engineers, David is
  19. usually working very hard and doesn't notice what time it is, so we
  20. have to remind him that it's time to leave for lunch.  Unfortunately,
  21. David can't hear us yelling to him in the Engineering department, so we
  22. needed an alternate way to tell David that is was time for lunch.  The
  23. only logical conclusion was to write a program that uses the Shared
  24. Socket Library to make a requester pop up on David's Workbench screen
  25. telling him it is lunch time.
  26.  
  27. To write a network application for the Shared Socket Library you will
  28. need two things:
  29.  
  30. 1) An understand the material in the article ``Developing Network
  31. Applications for the Amiga'' from the January/February 1992 issue of
  32. Amiga Mail.
  33.  
  34. 2) The Shared Socket Library include files and Autodocs (which are on
  35. the Network Developer's Disk and the Denver/Milano 1991 Devcon disks)
  36.  
  37. This application has to be broken into two pieces, a client and a
  38. server.  The client program will send out the notes.  On Amiga A, the
  39. user runs the client (SendNote), passing it a note string (like
  40. ``David, it's time to eat'') and a machine address for Amiga B.  The
  41. client then sends a note request off to the note server (ShowNote) on
  42. Amiga B.  When the note server gets the note request, it pops up an
  43. EasyRequest containing the note on Amiga B.  The server waits for the
  44. user to click an ``OK'' gadget, then sends back an acknowledgement to
  45. the client.
  46.  
  47. There are a couple of decisions to make about the application before
  48. coding anything.  First, there are two transport protocols to choose
  49. from: TCP and UDP.  To make things easier, this application uses TCP
  50. because it is a reliable protocol, so the application doesn't have to
  51. worry about making sure data makes it across the network.  The
  52. application uses a client/server model, but there are two of those
  53. models to choose from: iterative and concurrent.  Because this
  54. application does not need to handle more than one request at the same
  55. time, the iterative server is a better choice.  This also makes coding
  56. easier.
  57.  
  58.  
  59. The Application Protocol
  60.  
  61. The note program now needs an application protocol for sending
  62. information between the client and server.  When the client sends a
  63. request message to the server, it needs to send the note string.  It
  64. also would be nice to supply the text for the buttons that will pop up
  65. in the requester.  On the return trip from ShowNote to the SendNote
  66. client, the protocol only needs to define some return codes so the
  67. client can tell if the request worked and which button on the requester
  68. the remote user clicked.  Because the application needs to send
  69. different types of packets (one type containing a message request, the
  70. other type containing the response from the server), the protocol needs
  71. to have a way to specify a packet type.
  72.  
  73. The following structure is the packet that ShowNote and SendNote send
  74. back and forth to each other:
  75.  
  76.     struct NetNote
  77.     {
  78.         int nn_Code;
  79.         int nn_Retval;
  80.         char    nn_Text[200],
  81.                 nn_Button[40];
  82.     };
  83.  
  84.  
  85. On the trip from client to server, the nn_Code field is the message
  86. request packet type (NN_MSG), nn_Text is the note for the remote user,
  87. and nn_button is the text for the EasyRequest buttons.  On the return
  88. trip, nn_Code is either NN_ACK, if there was no error, or NN_ERR, if
  89. there was an error.  If there was no error, nn_Retval contains the
  90. number of the EasyRequest button that the user selected.
  91.  
  92. In this application, the same packet is passed back and forth for both
  93. legs of the trip (client to server and vice versa).  If the application
  94. required sending large chunks of data in one direction and small chunks
  95. of data in the other direction, it would be a good idea to use packets
  96. of different sizes.  Otherwise, if the application used only one packet
  97. size, the size of the packet would be huge compared to the size of the
  98. small chunk of data, which would unnecessarily loading the network.
  99.  
  100.  
  101. The ShowNote Server Application
  102.  
  103. Although the Amiga's Shared Socket Library is meant to be compatible
  104. with the Unix socket implementation, there are a couple of
  105. Amiga-specific quirks that an Amiga networked application has to take
  106. care of.  First, an Amiga application has to open the Shared Socket
  107. Library (socket.library).  Before calling any other functions in
  108. socket.library, the program has to call the socket.library function,
  109. setup_sockets().  ShowNote.c code has almost all of the network
  110. intialization material in the SS_Init() function.
  111.  
  112.  
  113.     /*
  114.     ** Attempt to open socket library and initialize socket environment.
  115.     ** If this fails, bail out to the non-returning AppPanic() routine.
  116.     */
  117.  
  118.     if (SockBase = OpenLibrary("inet:libs/socket.library",0L))
  119.     {
  120.         setup_sockets( 3, &errno );
  121.     }
  122.     else
  123.     {
  124.         AppPanic("Can't open socket.library!",0);
  125.     }
  126.  
  127.  
  128. Unlike most libraries, socket.library does not go in LIBS:.  Instead,
  129. it goes with the network-specific support files, in INET:, in a LIBS
  130. directory.  This location needs to be hardcoded into the application.
  131.  
  132. Next, create a socket:
  133.  
  134.     /*
  135.     ** Open the initial socket on which incoming messages will queue for
  136.     ** handling.  While the server is iterative, I do it this way so that
  137.     ** SIGBREAKF_CTRL_C will continue to function.
  138.     */
  139.  
  140.     int snum;
  141.  
  142.     if ((snum = socket( AF_INET, SOCK_STREAM, 0 )) == -1)
  143.     {
  144.         AppPanic("Socket Creation:",errno);
  145.     }
  146.  
  147.  
  148. and figure out what the well-known port number of the server is.  For
  149. testing purposes, SendNote and ShowNote use a hard-coded port number
  150. (8769).  If the application was installed on a network, each machine
  151. that ran the server would need an entry for the ``note'' service in the
  152. inet:db/services file.  In that case, both the server and the client
  153. would have to use getservbyname() to find the port number of the
  154. ``note'' service.
  155.  
  156. The server then needs to build a sockaddr_in structure describing
  157. itself.  The sockaddr_in contains all the information needed to map an
  158. initialized socket to a specific transport address on the Internet
  159. (thus the _in suffix, other network protocols have different suffixes).
  160. Before the socket can be assigned an Internet transport address, it
  161. needs to be initialized:
  162.  
  163.     struct sockaddr_in sockaddr;
  164.  
  165.     memset( &sockaddr, 0, len ); /* clear sockaddr */
  166.     sockaddr.sin_family = AF_INET;
  167.     sockaddr.sin_port = 8769;
  168.     sockaddr.sin_addr.s_addr = INADDR_ANY;
  169.  
  170.  
  171. Next, bind the socket to the well-known address in sockaddr.
  172.  
  173.  
  174.     if ( bind( snum, (struct sockaddr *)&sockaddr, len ) < 0 )
  175.     {
  176.         AppPanic("Socket Binding:",errno);
  177.     }
  178.  
  179.  
  180.     /*
  181.     ** Okay, the socket is as ready as it gets.  Now all we need to do is to
  182.     ** tell the system that the socket is open for business.
  183.     */
  184.  
  185.     listen( snum, 5 );
  186.  
  187.  
  188. The call to bind() takes the socket identifier and sockaddr_in
  189. structure and assigns a unique network identity (a transport address)
  190. to the socket, making it visible to the network.  The listen() call
  191. tells socket.library the socket is ready to receive incoming messages.
  192. The '5' in the listen() call tells the system the size of the
  193. connection request queue.
  194.  
  195. A connection request that a client sends to the server's socket goes
  196. into this queue and waits for the server to process it.  If requests
  197. arrive at the server's socket faster than the server can process them,
  198. the queue fills to capacity.  While the queue is full, the system
  199. rejects any other incoming connection requests.  ShowNote uses 5
  200. because that is the maximum allowed by Berkeley Sockets.
  201.  
  202.  
  203. Listening for Network and Amiga Events
  204.  
  205. Once back in the main() routine, the application is almost ready to
  206. start processing network events.  The only thing remaining is to decide
  207. what kind of events the server needs to hear about.  Most applications
  208. will need to be aware of both network and local Amiga events, which
  209. means separate masks need to be set up for both types of events.  On
  210. the network side, the mask needs to contain information on which
  211. sockets need responses.
  212.  
  213.     /* First, prepare the various masks for signal processing */
  214.     fd_set sockmask;
  215.  
  216.     FD_ZERO( &sockmask );
  217.     FD_SET( socket, &sockmask );
  218.  
  219. The sockmask variable will be used as a template to indicate what
  220. network events the application notices.  Since sockmask came off the
  221. stack and contains garbage, the FD_ZERO() call clears all its network
  222. signal bits.  The FD_SET() call sets the mask to listen for events
  223. relating to the socket that the server created earlier.  Everything is
  224. prepared, so the next step is entering the event loop itself.
  225.  
  226.  
  227.     long umask;
  228.     fd_set mask;
  229.  
  230.     while(1)
  231.     {
  232.         /*
  233.         ** Reset the mask values for another pass
  234.         */
  235.  
  236.         mask = sockmask;
  237.         umask = SIGBREAKF_CTRL_C;
  238.  
  239.         /*
  240.         ** selectwait is a combo network and Amiga Wait() rolled into
  241.         ** a single call.  It allows the app to respond to both Amiga
  242.         ** signals (CTRL-C in this case) and to network events.
  243.         **
  244.         ** Here, if the selectwait event is the SIGBREAK signal, we
  245.         ** bail and AppPanic() but otherwise its a network event.
  246.         */
  247.  
  248.         if (selectwait( 2, &mask, NULL, NULL, NULL, &umask ) == -1 )
  249.         {
  250.             AppPanic("CTRL-C:\nProgram terminating!",0);
  251.         }
  252.  
  253. Before an event occurs, the mask variable tells selectwait() which
  254. network events the server wants to receive.  After an event occurs, the
  255. mask variable indicates which socket triggered the network event.  The
  256. sockmask variable is used to reset mask back to its original mask value
  257. at the top of each pass through the event loop.
  258.  
  259. In addition to waiting on network events, selectwait() also waits on
  260. Exec signals for the current task.  For this example, the only Amiga
  261. event the server cares about is a Ctrl-C break (SIGBREAKF_CTRL_C).  The
  262. selectwait() function has a simple purpose, but due to the wide scope
  263. of network events, the function has a myriad of parameters and
  264. configurations. This example uses the bare minimum of what's possible
  265. using selectwait(), and many network-friendly applications will be able
  266. to get by using only a small subset of selectwait()'s potential.  In
  267. this case, the network event set is passed in mask, and the Amiga event
  268. mask is passed in umask.
  269.  
  270. If selectwait() returns with a value of -1, it means the Amiga event
  271. mask was the trigger.  Otherwise, a network event caused the function
  272. to return.  This example is simple enough that a Ctrl-C interrupt can
  273. be handled rather easily.
  274.  
  275.  
  276. Identifying Network Events and Talking to the Client
  277.  
  278. If selectwait() returned as a result of a network event, then a bit
  279. more detective work is needed to determine which socket caused the
  280. event.  The example only has to watch a single socket, so if that
  281. socket wasn't the cause, an error occurred.  In more complex servers,
  282. the server might have to check each of several sockets using the
  283. FD_ISSET() macro to determine which one caused the event.  There is
  284. also the possibility that more than a single event came in at the same
  285. time, so an application with many active sockets needs to take into
  286. account the possibility of multiple true results from FD_ISSET().
  287.  
  288.         if (FD_ISSET( socket, &mask ))
  289.         {
  290.             HandleMsg( socket );
  291.         }
  292.         else
  293.         {
  294.             AppPanic("Network Signal Error!",0);
  295.         }
  296.  
  297. The FD_ISSET() macro checks if there was activity on a socket by
  298. checking if the socket's bit is set in the socket mask passed to
  299. selectwait().  In this code, there is only a single active socket, so
  300. if that socket isn't the trigger then there was an error.
  301.  
  302. When a client tries to talk with a server, it first attempts to get a
  303. connection between itself and the server.  In this example, the server
  304. application returns from its selectwait() with that socket identified
  305. in the mask variable.  The server has to accept the connection:
  306.  
  307.     sockadd_in saddr;
  308.     int len;
  309.  
  310.     if (!(nsock = accept( sock, (struct sockaddr *)&saddr, &len )))
  311.     {
  312.         AppPanic("Accept:",errno);
  313.     }
  314.  
  315.  
  316. When the server calls accept(), the function attempts to form a
  317. connection with the client.  If it can do so, it returns a new socket
  318. identifier.  The new socket identifier corresponds to a new socket
  319. where the all future communication between the client and server will
  320. occur.  This allows concurrent servers to use an existing socket to
  321. establish new connections while carrying on other private client-server
  322. conversations.
  323.  
  324. The accept() function also passes back a sockaddr_in structure
  325. describing the client.  In this example, the server uses this address
  326. to figure out the client's host name:
  327.  
  328.     struct hostname *hent;
  329.     struct in_addr sad;
  330.     char *dd_addr, *hname, rname[80];
  331.  
  332.  
  333.     /*
  334.     ** Get the internet address out of the sockaddr_in structure and then
  335.     ** create a dotted-decimal format string from it.
  336.     */
  337.  
  338.     sad = saddr.sin_addr;
  339.     dd_addr = inet_ntoa(sad.s_addr);
  340.  
  341.     /*
  342.     ** Use the internet address to find out the machine's name
  343.     */
  344.  
  345.     if ( !( hent = gethostbyaddr( (char *) &sad.s_addr,
  346.                                   sizeof(struct in_addr),
  347.                                   AF_INET )))
  348.     {
  349.         AppPanic("Client resolution:\nAddress not in hosts db!", 0 );
  350.     }
  351.     hname = hent->h_name;
  352.  
  353.  
  354. Right now, if the server cannot identify the client's machine, it
  355. terminates with an error.  While this may seem a bit drastic, it does
  356. prevent anonymous messages from being sent across the system.  Coding a
  357. secure client and server requires more complex protocols and involves
  358. many other issues which are beyond the scope of this article.
  359.  
  360. After doing a little formatting of the address and name information,
  361. the HandleMsg() function begins the actual process of communicating
  362. with the client.
  363.  
  364.  
  365.     int nsock;
  366.     struct NetNote in;
  367.  
  368.     /*
  369.     ** Okay, now the waiting packet needs to be removed from the connected
  370.     ** socket that accept() gave back to us.  Verify its of type NN_MSG and
  371.     ** if not, set return type to NN_ERR.  If it is, then display it and
  372.     ** return an NN_ACK message.
  373.     */
  374.  
  375.     recv( nsock, (char *)&in, sizeof(struct NetNote), 0 );
  376.     if (in.nn_Code == NN_MSG)
  377.     {
  378.         DisplayBeep(NULL);  /* DisplayBeep() to get the user's attention */
  379.         DisplayBeep(NULL);
  380.         retv = DoER( rname, (char *)&in.nn_Text, (char *)&in.nn_Button );
  381.         in.nn_Code = NN_ACK;
  382.         in.nn_Retval = retv;
  383.     }
  384.     else
  385.     {
  386.         in.nn_Code = NN_ERR;
  387.     }
  388.  
  389.  
  390. The recv() function removes a struct NetNote sized amount of data from
  391. the socket nsock and places that information in a buffer.  Once the
  392. message packet is in the buffer, the server verifies it is a proper
  393. packet by checking the nn_Code.  The checking isn't really necessary
  394. since the example uses a reliable protocol (TCP) and there is only one
  395. type of packet the server can receive.  After checking the packet, the
  396. server prepared the nn_Code field for the return trip to the client.
  397. If there was something wrong with the packet, the server sets nn_Code
  398. to NN_ERR, otherwise the server sets nn_Code to NN_ACK. If there was no
  399. error, the server extracts the message and button text from the NetNote
  400. packet.  The server passes them plus name and address information for
  401. the client to the DoER() routine.  The DoER() routine creates a system
  402. EasyRequest, displays it, and returns a value which corresponds to the
  403. button which was pressed.  The return information on which button was
  404. pressed is encoded into the nn_Retval field of the NetNote packet,
  405. which is then ready to be sent back to the client.
  406.  
  407. The prepared packet is sent back to the client using the send()
  408. function:
  409.  
  410.     /*
  411.     ** Having dealt with the message one way or the other, send the message
  412.     ** back at the remote, then disconnect from the remote and return.
  413.     */
  414.  
  415.     send( nsock, (char *)&in, sizeof(struct NetNote), 0 );
  416.     s_close( nsock );
  417.  
  418.  
  419. The HandleMsg() function then closes nsock using s_close(). The packet
  420. protocol between the client and server in this application is defined
  421. so there are no cases where the client would send another message, so
  422. it can close the socket and break the connection.  The
  423. acknowledgement/error packet will arrive at the client regardless of
  424. whether the connection is still active or not, at least under TCP/IP.
  425. HandleMsg() returns to the main() routine and event loop.
  426.  
  427. Once back in the main event loop, the server will continue connecting
  428. and responding to client messages until it receives a Ctrl-C interrupt
  429. or an error condition occurs.
  430.  
  431.  
  432. Starting the ShowNote Server
  433.  
  434. The ShowNote server should run as a backround CLI process.  To start
  435. it, type the following line at a CLI prompt:
  436.  
  437. run >nil: shownote
  438.  
  439. You can start up the server when you boot your Amiga by adding that
  440. line to s:user-startup.
  441.  
  442.  
  443. The SendNote Client Application
  444.  
  445. The client application, SendNote, sends a message for a server to pop
  446. up on its display.  While the server is an Intuition program in that it
  447. uses a requester to display the messages and get responses, the client
  448. (SendNote) is strictly a CLI-based application.  SendNote parses its
  449. command line using the AmigaDOS 2.0 ReadArgs() call.  For more
  450. information on ReadArgs(), see the AmigaDOS Manual, 3rd Edition from
  451. Bantam, the article ``Standard Command Line Parsing'' from the May/June
  452. 1991 issue of Amiga Mail, or the DOS library includes and Autodocs.
  453. There is very little difference in the application-wide setup of
  454. sockets between the client and server.  Both must open socket.library
  455. and call setup_sockets() to initialize the socket environment.  One
  456. minor difference is the number of sockets the client initializes.  The
  457. client only needs a single socket to establish a connection to the
  458. server, where the server requires at least two (one for receiving
  459. connection requests and one for talking to a client).
  460.  
  461. The FinalExit() routine is both the client's error handler and its
  462. shutdown routine.  Since the client doesn't need to maintain so much
  463. operating information, the shutdown routine is rather simple, and can
  464. be used for both errors and normal termination.
  465.  
  466.  
  467. Resolving the Target (Host) Address
  468.  
  469. The client gets a string back from the ReadArgs() call which contains
  470. the hostname in either dotted-decimal notation or ASCII form.  The
  471. clint needs to convert that string to a usable form.
  472.  
  473.  
  474.     struct sockaddr_in serv;
  475.     struct hostent *host;
  476.     char *hostnam, *text, *button;
  477.  
  478.     /*
  479.     ** First we need to try and resolve the host machine as an IP/Internet address.
  480.     ** If that fails, fall back to seaching the hosts file for it.  Later versions of
  481.     ** gethostbyname() may use DNS to find a host name, rather than searching the hosts file.
  482.     */
  483.  
  484.  
  485.  
  486.     bzero( &serv, sizeof(struct sockaddr_in) );
  487.     if ( (serv.sin_addr.s_addr = inet_addr(hostnam)) == INADDR_NONE )
  488.     {
  489.         /*
  490.         ** Okay, the program wasnt handed a dotted decimal address,
  491.         ** so we check and see if it was handed a machine name.
  492.         */
  493.  
  494.         if ( (host = gethostbyname(hostnam)) == NULL )
  495.         {
  496.             printf("Host not found: %s\n",host);
  497.             FinalExit( RETURN_ERROR );
  498.         }
  499.  
  500.         /*
  501.         ** It does indeed have a name, so copy the addr field from the
  502.         ** hostent structure into the sockaddr structure.
  503.         */
  504.  
  505.         bcopy( host->h_addr, (char *)&serv.sin_addr, host->h_length );
  506.     }
  507.  
  508.  
  509. After clearing out the serv sockadd_in structure, the client tries to
  510. convert the host name string (hostnam) it got from its command line
  511. from dotted-decimal to an IP address block using the inet_addr()
  512. function.  If this fails, the server treats the string hostnam as an
  513. ASCII string containing a host name, and tries to get a normal IP
  514. address using gethostbyname().  This will search the hosts file
  515. (inet:db/hosts) for a matching entry.  Future versions of
  516. gethostbyname() may use DNS (domain name system), which allows
  517. gethostbyname() to ask a server for host information rather than
  518. looking it up in a hosts file.
  519.  
  520. If it is successful, gethostbyname() returns a pointer to a hostent
  521. structure.  It requires a little work to to convert this hostent
  522. structure to a sockaddr_in (IP socket address) structure.  There is a
  523. sockaddr structure embedded inside the hostent structure which can be
  524. used as a sockaddr string in this case.  The call to bcopy() copies
  525. that embedded sockaddr structure into the client's sockaddr_in buffer.
  526.  
  527.  
  528. Locating the Server Port and Connecting to It
  529.  
  530. The next step is to find the server's port number.  In the case of this
  531. example, the port name is hardcoded into the server and client.  A real
  532. networked application should have an entry for the ``note'' service in
  533. the inet:db/services file.  The code below finds the port number in the
  534. client machine's inet:db/services file.
  535.  
  536.  
  537.     struct servent *servptr;
  538.     char servnam[] = "note";
  539.  
  540.     if ((servptr = getservbyname( servnam, "tcp" )) == NULL)
  541.     {
  542.         printf("%s not in inet:db/services list!",servnam);
  543.         FinalExit( RETURN_ERROR );
  544.     }
  545.     serv.sin_port = servptr->s_port;
  546.  
  547.  
  548.     /*
  549.     ** This tells the system the socket in question is an Internet socket
  550.     */
  551.  
  552.     serv.sin_family = AF_INET;
  553.  
  554.  
  555. Since the client and server are running on top of an IP (Internet
  556. Protocol) system, the client needs to specify AF_INET in its call to
  557. socket() just as the server did.
  558.  
  559.     /*
  560.     ** Initialize the socket
  561.     */
  562.  
  563.     if ( (sock = socket( AF_INET, SOCK_STREAM, 0 )) < 0 )
  564.     {
  565.         printf("socket gen: %s\n", strerror(errno));
  566.         FinalExit( RETURN_ERROR );
  567.     }
  568.  
  569.  
  570. The client socket is initialized and ready.  Because the client knows
  571. the IP address of the server's machine and the service number of the
  572. ``note'' service, the client knows the well-known transport address so
  573. it can attempt to establish a connection with the server:
  574.  
  575.     /*
  576.     ** Connect the socket to the remote socket, which belongs to the
  577.     ** server, and which will "wake up" the server.
  578.     */
  579.  
  580.     if ( connect( sock,
  581.                   (struct sockaddr *) &serv,
  582.                   sizeof(struct sockaddr) ) < 0 )
  583.     {
  584.         printf("connect: %s\n", strerror(errno));
  585.         s_close( sock );
  586.         FinalExit( RETURN_ERROR );
  587.     }
  588.  
  589.  
  590. The connect() call contacts the server across the network and attempts
  591. to form a connection.  There are many things which can go wrong at this
  592. point, and any return less than zero indicates an error condition.  If
  593. an error does occur, the socket.library function strerror() converts
  594. the error number in errno into a readable error message, which is then
  595. displayed by the client.  In case of an error, the client already has a
  596. socket open, so it then must close the socket and terminate using the
  597. application's error handler, FinalExit().
  598.  
  599. Once the connection is established, the client needs to prepare the
  600. note request packet.  Since the call to ReadArgs() has already parsed
  601. everything, the client only has to copy the strings into a NetNote
  602. structure:
  603.  
  604.     struct NetNote out;
  605.  
  606.     out.nn_Code = NN_MSG;
  607.     strcpy( (char *)&out.nn_Text, text );
  608.     strcpy( (char *)&out.nn_Button, button );
  609.  
  610. The client has filled in all the relevant fields of the NetNote
  611. structure, so it is ready to send.  The server will fill in the
  612. nn_Retval field before passing it back to the client, so the client
  613. doesn't need to fill it in for the client-to-server leg of the trip.
  614. All that remains is to transfer the packet across the network:
  615.  
  616.     send( sock, (char *)&out, sizeof(struct NetNote), 0 );
  617.  
  618.     printf("\nMessage sent to %s...waiting for answer...\n", hostnam );
  619.  
  620.     /*
  621.     ** Wait for either acknowlegde or error.
  622.     */
  623.  
  624.     recv( sock, (char *)&out, sizeof(struct NetNote), 0 );
  625.  
  626.  
  627. Now the client has to wait for the server to respond.  This is one of
  628. the few points where the client can hang forever.  If the server
  629. receives the message, and never replies back, the client will never
  630. stop waiting for a reply.  A real application would time-out if the
  631. server didn't respond within a certain time interval.  One way to do
  632. this is using a selectwait() which breaks when triggered by a
  633. timer.device event.  We'll leave that as an exercise.
  634.  
  635. The call to recv() waits until the server sends back a reply.  Once the
  636. client receives the reply, the client has to check the NetNote
  637. structure's nn_Code field.  If it's NN_ERR, an error occurred, and the
  638. client terminates through the FinalExit() handler.  If an NN_ACK packet
  639. comes back from the server, the nn_Retval field contains the number of
  640. the button the user pressed on the ShowNote requester.
  641.  
  642. The server gets the button number directly from the EasyRequest()
  643. function that the server uses to pop up the requester.  The buttons on
  644. the requester are numbered from 1, increasing left to right, except for
  645. the rightmost button, which is button zero.  If there is only one
  646. button, that button will return a zero (it's the furthest right).
  647.  
  648. After the client gets the packet back from the server, the client's
  649. task is complete.  It only has to clean up after itself.
  650.  
  651.  
  652. Starting the SendNote Client
  653.  
  654. The SendNote command has the following template, and uses the AmigaDOS
  655. 2.0 ReadArgs() call to parse the CLI command line:
  656.  
  657. SENDNOTE HOST/A,TEXT,BUTTON
  658.  
  659. The HOST argument is the address of the server machine.  It is either a
  660. dotted-decimal notation address or its ASCII  host name from the
  661. inet:db/hosts file.  The TEXT argument is the string the server will
  662. pop up in its requester.  The BUTTON argument is the ASCII string that
  663. will appear in the requester button.  For example:
  664.  
  665. sendnote 123.123.123.1 "I've fallen and I can't get up!" "Help me!"
  666.  
  667. displays a requester on the machine whose address is 123.123.123.1,
  668. with the text of the requester saying "I've fallen and I can't get up!"
  669. and a button labeled "Help me!".  If that machine is not running the
  670. server, then SendNote just displays an error message and terminates.
  671. If the inet:db/hosts file on the machine running SendNote has an entry
  672. that gives 123.123.123.1 the name "foo", then
  673.  
  674. sendnote foo "I've fallen and I can't get up!" "Help me!"
  675.  
  676. will have the exact same effect as the previous form of the command.
  677. There are also default values for both the TEXT and BUTTON arguments.
  678. If the user doesn't provide an argument for BUTTON, then ShowNote
  679. defaults to "OK".  If both TEXT and BUTTON are missing, the TEXT
  680. argument defaults to "==PING!==".  To give the user on the server
  681. machine several buttons to choose from, put several strings in the
  682. BUTTON argument delimited by `|` characters, like this:
  683.  
  684. sendnote foo "Is it time yet?" "yes|no|maybe"
  685.  
  686. That's all there is to the client application.  Because the
  687. socket.library routines take care of so much of the network ``nitty
  688. gritty'', the application code can deal with networks in a
  689. straight-forward and simple manner.  Two applications (client and
  690. server), each around 8K bytes in size, are able to implement a complete
  691. and working intranetwork communication system (albeit a very simplistic
  692. one).  If you are interested in doing more serious development using
  693. the AS225 software (and thus IP and TCP/UDP), you should take a look at
  694. more advanced texts.  A good start is Unix Network Programming by W. R.
  695. Stevens (Prentice-Hall, ISBN 0-13-949876-1).  It covers many aspects of
  696. network protocol and application design, as well as explaining quite a
  697. bit about ``Berkeley Sockets'' which socket.library implements.
  698.  
  699.  
  700.  
  701.  
  702.  
  703.